package net.tv90.volunteerserveradmin.aop;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson2.JSON;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.tv90.volunteerserveradmin.annotation.OptLogger;
import net.tv90.volunteerserveradmin.manager.AsyncManager;
import net.tv90.volunteerserveradmin.manager.factory.AsyncFactory;
import net.tv90.volunteerserveradmin.model.domain.OperationLog;
import net.tv90.volunteerserveradmin.utils.IpUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Objects;


/**
 * AOP记录操作日志
 **/
@Aspect
@Component
public class OptLogAspect {

    /**
     * 请求开始时间
     */
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * 设置操作日志切入点，记录操作日志，在注解的位置切入代码
     */
    @Pointcut("@annotation(net.tv90.volunteerserveradmin.annotation.OptLogger)")
    public void optLogPointCut() {
    }

    /**
     * 前置通知
     */
    @Before("optLogPointCut()")
    public void doBefore() {
        // 记录请求开始时间
        startTime.set(System.currentTimeMillis());
    }

    /**
     * 连接点正常返回通知，拦截用户操作日志，正常执行完成后执行， 如果连接点抛出异常，则不会执行
     *
     * @param joinPoint 切面方法的信息
     * @param result    返回结果
     */
    @AfterReturning(value = "optLogPointCut()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 获取操作
        Tag api = (Tag) signature.getDeclaringType().getAnnotation(Tag.class);
        Operation apiOperation = method.getAnnotation(Operation.class);
        OptLogger optLogger = method.getAnnotation(OptLogger.class);
        // 获取request
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
        // 日志保存到数据库
        OperationLog operationLog = new OperationLog();
        // 操作模块
        operationLog.setModule(api.name());
        // 操作类型
        operationLog.setType(optLogger.value());
        // 请求URI
        operationLog.setUri(request.getRequestURI());
        // 获取请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        // 获取请求的方法名
        String methodName = method.getName();
        methodName = className + "." + methodName;
        // 请求方法
        operationLog.setName(methodName);
        // 操作描述
        operationLog.setDescription(apiOperation.description());
        // 过滤掉不能序列化的参数，如 HttpServletRequest, HttpServletResponse
        Object[] filteredArgs = Arrays.stream(joinPoint.getArgs())
                .filter(arg -> !(
                        arg instanceof HttpServletRequest ||
                        arg instanceof HttpServletResponse))
                .map(arg -> {
                    if (arg instanceof MultipartFile) {
                        return ((MultipartFile) arg).getOriginalFilename(); // 只保存文件名
                    }
                    return arg;
                }).toArray();
        operationLog.setParams(JSON.toJSONString(filteredArgs));
        operationLog.setMethod(request.getMethod());
        // 对返回值进行处理，避免对response再次序列化
        String contentType = request.getContentType();
        boolean isDownload = contentType != null && (contentType.contains("text/csv") || contentType.contains("application/octet-stream"));
        if (result == null || result instanceof HttpServletResponse || isDownload) {
            // 对于导出类接口或无返回值接口，不对result进行JSON序列化
            operationLog.setData(null);
        } else {
            operationLog.setData(JSON.toJSONString(result));
        }
        // 请求用户ID
        try {
            operationLog.setUserId(StpUtil.getLoginIdAsLong());
        } catch (NotLoginException ignored) {
        }
        // 操作ip和操作地址
        String ip = IpUtils.getIpAddress(request);
        operationLog.setIpAddress(ip);
        operationLog.setIpSource(IpUtils.getIpSource(ip));
        // 执行耗时
        operationLog.setTimes(System.currentTimeMillis() - startTime.get());
        operationLog.setCreateTime(LocalDateTime.now());
        startTime.remove();
        // 保存到数据库
        AsyncManager.getInstance().execute(AsyncFactory.recordOperation(operationLog));
    }
}